<?php
/**
 * Nordic Repository
 * 
 * LICENSE
 * 
 * The new BSD license is applied on this source-file. For further
 * information please visit http://license.nordic-dev.de/newbsd.txt
 * or send an email to andre.moelle@gmail.com.
 */

/**
 * Necessary includes.
 */
Zend_Loader::loadClass('Nordic_Security');
Zend_Loader::loadClass('Nordic_Upload_Exception');
Zend_Loader::loadClass('Nordic_Upload_Filter');

/**
 * This class contains everything what is necessary for file-uploads.
 * 
 * But it only handles the uploaded file after uploading by providing
 * some useful method.
 * 
 * @category   Nordic
 * @package    Nordic_Upload
 * @copyright  2007 Nordic Development
 * @license    http://license.nordic-dev.de/newbsd.txt (New-BSD license)
 * @author     Andre Moelle <andre.moelle@gmail.com>
 * @version    $Id: Upload.php 3 2007-07-08 09:21:42Z andre.moelle $
 */
final class Nordic_Upload
{
	/**
	 * Directory where the uploaded file will reside.
	 * 
	 * @var String
	 */
	private $_destination = '';
	
	/**
	 * Filters which should be applied.
	 * 
	 * This array contains filter-name for checking if the source is
	 * a valid object. If there are no filters given in this array no
	 * checks will be executed. If there are one or more filters in
	 * this array, at least one of these filters must apply.
	 * 
	 * @var Array
	 */
	private $_filters = array();
	
	/**
	 * Maximum file-size.
	 * 
	 * This size is an amount of bytes.
	 * 
	 * @var Integer
	 */
	private $_size;
	
	/**
	 * Path to the source-file.
	 * 
	 * This file has to be uploaded via PHPs upload mechanisms since
	 * the move_uploaded_file only processes such files.
	 * 
	 * @var String
	 */
	private $_source = '';
	
	/**
	 * Number of possible tries for random filenames.
	 * 
	 * @var Integer
	 */
	private $_tries = 10;
	
	/**
	 * Error-code if no extension was provided.
	 * 
	 * @var Integer
	 */
	const NO_EXTENSION = 1;
	
	/**
	 * Error-code if no filter matched.
	 * 
	 * @var Integer
	 */
	const NO_MATCH = 2;
	
	/**
	 * This is the error-code if there is already one file with
	 * the passed name.
	 * 
	 * @var Integer
	 */
	const OVERWRITE = 4;
	
	/**
	 * Constructor.
	 * 
	 * Requires the path of the source-file. If the source-file does
	 * not exist a "Nordic_Upload_Exception" will be thrown. You
	 * can additionally assign the destination-directory.
	 * 
	 * @throws Nordic_Upload_Exception
	 * @param String $source path to source-file
	 * @param String $destination destination-directory
	 * @return void
	 */
	public function __construct ($source, $destination = '')
	{
		if(!file_exists(gv($source, '')))
		{
			throw new Nordic_Upload_Exception ('Invalid file given');
		}
		
		$this->_source = $source;
		
		if(!$destination)
		{
			$this->_destination = UPLOAD_PATH;
		}
		else
		{
			$this->setPath($destination);
		}
	}
	
	/**
	 * Empties the filters.
	 * 
	 * @param void
	 * @return void
	 */
	public function emptyFilters ()
	{
		$this->_filters = array();
	}
	
	/**
	 * Adds a filter to the internal array.
	 * 
	 * You can pass either an array or a single string which
	 * represent the filter-names. Returned is the amount of
	 * added filters.
	 * 
	 * @throws Nordic_Upload_Exception
	 * @param mixed $filters
	 * @return Integer
	 */
	public function filter ($filters)
	{
		if(!is_array($filters))
		{
			if(!Nordic_Upload_Filter::checkFilter($filters))
			{
				throw new Nordic_Upload_Exception('Upload-Filter "' . $filters . '" is not valid');
			}
			
			$this->_filters[] = ucfirst(strtolower($filters));
			
			return 1;
		}
		
		foreach($filters as $filter)
		{
			$result += $this->filter($filter);
		}
		
		return $result;
	}
	
	/**
	 * Returns all filters as an array.
	 * 
	 * @param void
	 * @return Array
	 */
	public function getFilters ()
	{
		return $this->_filters;
	}
	
	/**
	 * Removes the filter with the passed name.
	 * 
	 * @param String $filter filter-name
	 * @return Boolean
	 */
	public function removeFilter ($filter)
	{
		$filter = ucfirst(strtolower($filter));
		
		foreach($this->_filters as $key => $value)
		{
			if($value != $filter)
			{
				continue;
			}
			
			unset($this->_filters[$key]);
			
			return true;
		}
		
		return false;
	}
	
	/**
	 * Sets the max-size of the file.
	 * 
	 * Note that this method does not apply the ini-directive
	 * "upload_max_filesize" since the upload happend before running
	 * the framework.
	 * 
	 * @param Integer $size maximal file-size of the uploaded file
	 */
	public function setSize ($size)
	{
		$this->_size = $size;
	}
	
	/**
	 * Sets the path of the destination-directory.
	 * 
	 * Note that it only sets the directory where the uploaded file
	 * will be stored. The filename is set by using the
	 * upload-method.
	 * 
	 * @param String $path path to destination-directory
	 * @return void
	 */
	public function setPath ($dir)
	{
		/**
		 * @TODO: Remove UPLOAD_PATH.
		 */
		
		$this->_destination = 
			  UPLOAD_PATH . ($dir[0] == DS ? substr($dir, 1) : $dir) 
			. (substr($dir, -1) == DS ? '' : DS);
	}
	
	/**
	 * Here the upload-progress is finished.
	 * 
	 * If you do not specify any filename the filename is generated
	 * randomly using "Nordic_Security". The extension
	 * 
	 * <code>
	 * $opt = array(
	 * 	'filename' => 'filename.without.extension',
	 * 	'extension' => 'extension-without-leading-period',
	 * 	'overwrite' => true // determines if an old file will be overwritten
	 * );
	 * </code>
	 * 
	 * @throws Nordic_Upload_Exception
	 * @param Array $args optional options
	 * @return String
	 */
	public function upload ($opt = array())
	{
		$filename = gv($opt['filename'], '');
		$extension = gv($opt['extension'], '');
		$overwrite = (bool)gv($opt['overwrite'], true);
		$match = false;
		$error = 0;
		
		if(count($this->getFilters()))
		{
			foreach($this->getFilters as $name)
			{
				$filter = Nordic_Upload_Filter::factory(
					$name,
					$this->_source
				);
				
				if(!$filter->process())
				{
					continue;
				}
				
				if($extension == '')
				{
					$extension = $filter->getExtension();
				}
				
				$match = true;
				break;
			}
			
			$error |= self::NO_MATCH;
		}
		
		if($match && !$extension)
		{
			$error |= self::NO_EXTENSION;
		}
		if(!$overwrite && $filename && file_exists($this->_destination) . $filename)
		{
			$error |= self::OVERWRITE;
		}
		if(!$filename)
		{
			$tries = $this->_tries;
			
			do
			{
				$filename = Nordic_Security::createString();
				
				if(!(--$tries))
				{
					if(!$overwrite)
					{
						$error |= self::OVERWRITE;
					}
					break;
				}
			}
			while(glob($this->_destination . $filename . '.*') !== false);
		}
		
		if($error)
		{
			throw new Nordic_Upload_Exception('Errors occured while finishing upload-process', $error);
		}
		
		return move_uploaded_file(
			$this->_source,
			$this->_destination . $filename . '.' . $extension
		);
	}
}
?>